/******************************************************************************
 * $Id: handlers_tree.c,v 1.12 2004/09/22 01:14:22 jimb Exp $
 * @file
 * @author	Jim Brooks http://www.jimbrooks.org
 * @brief	Tree widget handlers/callbacks.
 ******************************************************************************/

#include "common.h"
#include "handlers_tree.h"

#include "misc.h"
#include "funcmisc.h"
#include "treefunc.h"
#include "history.h"
#include "tags.h"
#include "statusbar.h"
#include "text.h"
#include "widgets.h"

static guint	id_HyperjumpViaTreeNodeOrItem_cb		= CALLBACK_ID_OFF;
static guint	id_HyperjumpToModuleViaModuleTree_cb	= CALLBACK_ID_OFF;
static guint	id_HandlerFuncDefTreeExpand_cb			= CALLBACK_ID_OFF;
static guint	id_HandlerModuleTreeExpand_cb			= CALLBACK_ID_OFF;

static lock_t	HandlerModuleTreeExpand_cb_lock			= UNLOCKED;
static lock_t	HandlerFuncDefTreeExpand_cb_lock		= UNLOCKED;

/*****************************************************************************
 * Reset tree state (for attempting crash recovery).
 *****************************************************************************/
void
ResetTrees( void )
{
   UnscheduleIdleCallback( &id_HyperjumpViaTreeNodeOrItem_cb );
   UnscheduleIdleCallback( &id_HyperjumpToModuleViaModuleTree_cb );
   UnscheduleIdleCallback( &id_HandlerFuncDefTreeExpand_cb );
   UnscheduleIdleCallback( &id_HandlerModuleTreeExpand_cb );

   Unlock( &HandlerModuleTreeExpand_cb_lock );
   Unlock( &HandlerFuncDefTreeExpand_cb_lock );

   return;
}

/*****************************************************************************
 * Lock a tree function.
 * The purpose is to prevent nesting critical functions which otherwise could
 * happen if an event is dequeued while another event is still being processed.
 * @return Returns FALSE if caller is forbidden to execute the function.
 * Returns TRUE  if caller is allowed to execute the now-locked function.
 *****************************************************************************/
gboolean
LockTreeFunc( gboolean* pBusyFlag ) /* ### MUST BE A STATIC VAR ### */
{
   if ( *pBusyFlag  ||  IsAnyTreeFunctionBusy()  ||  IsAnyMajorFunctionBusy(0) )
   {
     /*
      * Locked (busy), caller forbidden.
      */
      StatusbarBusy();
      return FALSE;
   }
   else
   {
     /*
      * Unlocked (not busy), caller allowed.
      */
      *pBusyFlag = TRUE;
      SetSensitivityModuleFuncTrees( FALSE, DONT_HIDE_IF_INSENSITIVE );
      return TRUE;
   }
}

/*****************************************************************************
 * Unlock a tree function.
 *****************************************************************************/
void
UnlockTreeFunc( gboolean* pBusyFlag ) /* ### MUST BE A STATIC VAR ### */
{
   *pBusyFlag = FALSE;
   SetSensitivityModuleFuncTrees( TRUE, DONT_HIDE_IF_INSENSITIVE );
}

/*****************************************************************************
 * Handler for when an item in a tree is selected.
 * This handles a GtkTree "select_child" signal.
 *
 * @remarks GTK+ used to segfault in gtk_tree_item_deselect() if the user clicked
 * the tree several times before this handler finished
 * (which could happen while loading a module from disk).
 * Problem was a consequence of ProcessPendingEvents() being called
 * while tree events were enqueued.  Problem is solved by deferring
 * Hyperjump*() as idle calls, so when Hypersrc*() execute,
 * there won't be any pending tree events.
 *****************************************************************************/
void
HandlerTreeItemSelected( GtkTree*   pWidgetTree,
                         GtkWidget* pWidgetItem,
                         gpointer   unused )
{
   static gboolean  busy          = FALSE;
   tag_t*           pTag          = NULL;
   funcDef_t*       pFuncDef      = NULL;
   pTag_pFuncDef_t* pTag_pFuncDef = NULL;

g_return_if_fail( pWidgetTree  &&  pWidgetItem );

  /*
   * Prevent nesting critical functions.
   */
   if ( !LockTreeFunc( &busy ) ) return;

  /*
   * Is the tree item actually a subtree?
   * If it is, a following signal will invoke HandlerSubtreeSelected()
   * which will do the same action that would have been done here.
   */
   if ( GTK_TREE_ITEM_SUBTREE(pWidgetItem) != NULL )
      goto quit;

  /*
   * Is the tree item selected?
   * Modified: change GTK_TREE_SELECTION to GTK_TREE_SELECTION_OLD
   */
   //if ( ! g_list_find( GTK_TREE_SELECTION( GTK_TREE_ROOT_TREE(pWidgetTree) ),
   if ( ! g_list_find( GTK_TREE_SELECTION_OLD( GTK_TREE_ROOT_TREE(pWidgetTree) ),
                       pWidgetItem ) )
      goto quit;

  /*
   * Extract the tag (attached as object data) from the tree item.
   */
   pTag = (tag_t*) gtk_object_get_data( GTK_OBJECT(pWidgetItem), "ptag" );
   if ( pTag == NULL )
   {
      DebugMsg( "Couldn't extract tag from subtree item, continuing..." );
      goto quit;
   }

  /*
   * Extract the pointer to funcDef_t (may or may not be attached to object).
   * The funcDef_t is desirable because its line number information
   * is more accurate than tag_t (hash collisions aren't chained,
   * so a tag_t could point to wrong same-named function).
   * See ModuleTreeDecorateSubtree().
   */
   pFuncDef = (funcDef_t*) gtk_object_get_data( GTK_OBJECT(pWidgetItem), "pfd" );

  /*
   * Hyperjump to the tagged line (deferred to an idle callback).
   */
   pTag_pFuncDef           = (pTag_pFuncDef_t*) g_malloc( sizeof(pTag_pFuncDef_t) );
   pTag_pFuncDef->pTag     = pTag;
   pTag_pFuncDef->pFuncDef = pFuncDef;

   ScheduleIdleCallback( &id_HyperjumpViaTreeNodeOrItem_cb,
                         HyperjumpViaTreeNodeOrItem_cb,
                         (gpointer) pTag_pFuncDef );

quit:
   UnlockTreeFunc( &busy );
   return;
}

/*****************************************************************************
 * Handler for when a subtree (node) is selected.
 * This handles a GtkItem "select" signal (an ancestor class of a subtree).
 * @remarks See HandlerTreeItemSelected().
 *****************************************************************************/
void
HandlerSubtreeSelected( GtkTree*   pWidgetItem,	/* refers to a subtree (node), not a leaf (tree item) */
                        gpointer   unused )
{
   static gboolean	busy		= FALSE;
   tag_t*			pTag		= NULL;
   char*			pModuleName = NULL;
   funcDef_t*       pFuncDef    = NULL;

g_return_if_fail( pWidgetItem );

  /*
   * Prevent nesting critical functions.
   */
   if ( !LockTreeFunc( &busy ) ) return;

  /*
   * Extract either a tag or module name from the subtree (attached as object data).
   * If this is the module tree, object data will be a module name.
   * If this is the func tree,   object data will be a tag, and possibly funcDef_t.
   * See ModuleTreeDecorateSubtree() concerning funcDef_t.
   */
   pTag        = (tag_t*)     gtk_object_get_data( GTK_OBJECT(pWidgetItem), "ptag" );
   pModuleName = (char*)      gtk_object_get_data( GTK_OBJECT(pWidgetItem), "module" );
   pFuncDef    = (funcDef_t*) gtk_object_get_data( GTK_OBJECT(pWidgetItem), "pfd" ); 
   if ( pTag )
   {
     /*
      * Hyperjump to the tagged line.
      */
      pTag_pFuncDef_t* pTag_pFuncDef;

      pTag_pFuncDef           = (pTag_pFuncDef_t*) g_malloc( sizeof(pTag_pFuncDef_t) );
      pTag_pFuncDef->pTag     = pTag;
      pTag_pFuncDef->pFuncDef = pFuncDef;   /* may or may not be NULL */

      ScheduleIdleCallback( &id_HyperjumpViaTreeNodeOrItem_cb,
                            HyperjumpViaTreeNodeOrItem_cb,
                            (gpointer) pTag_pFuncDef );
      goto quit;
   }
   else if ( pModuleName )
   {
     /*
      * Hyperjump to the corresponding module.
      */
      ScheduleIdleCallback( &id_HyperjumpToModuleViaModuleTree_cb,
                            HyperjumpToModuleViaModuleTree_cb,
                            (gpointer) pModuleName );
      goto quit;
   }

   DebugMsg( "Couldn't extract tag nor module from subtree, continuing anyway..." );

quit:
   UnlockTreeFunc( &busy );
   return;
}

/*****************************************************************************
 * Idle callback to show the line of src code corresponding
 * to a selected tree node or item.
 *****************************************************************************/
gint
HyperjumpViaTreeNodeOrItem_cb( gpointer pTag_pFuncDef /* g_malloc-ed */ )
{
   GtkWidget* pWidgetTextActivePrev = pWidgetTextActive;
   tag_t*     pTag                  = ((pTag_pFuncDef_t*)pTag_pFuncDef)->pTag;
   funcDef_t* pFuncDef              = ((pTag_pFuncDef_t*)pTag_pFuncDef)->pFuncDef;
   int        line                  = 0;

   g_free( pTag_pFuncDef );	/* no longer needed */

g_return_val_if_fail( pTag, FALSE );

  /*
   * Prevent nesting critical functions.
   */
   if ( !LockTreeFunc( &busy_HyperjumpViaTreeNodeOrItem_cb ) )
      return TRUE; /* tell GTK+ to call back again */

  /*
   * Turn off this idle callback now.  Otherwise, it will be called again
   * when the following functions call ProcessPendingEvents()!
   */
   UnscheduleIdleCallback( &id_HyperjumpViaTreeNodeOrItem_cb );

  /*
   * Prepare to load the module referenced by the tag.
   * Return if the tag was fabricated (there is no module).
   */
   if ( IsTagFabricated( pTag ) )
   {
      PrintStatusbarThirds( "'", pTag->pName, "' is not tagged" );
      goto quit;
   }

  /*
   * Record current cursor position into tag history.
   */
   RecordCursorInTagHistory( "(recorded position)" );

  /*
   * Should the tagged line be shown in the main or aux text widget?
   */
   SwitchActiveTextWidget( DONT_SWITCH_UNLESS_AUX_TEXT_KEY );

  /*
   * If funcDef_t is available, then use its line number information,
   * which is more accurate than tag_t.  See ModuleTreeDecorateSubtree().
   */
   if ( pFuncDef )
   {
      line = pFuncDef->lineStart ;  //- 1;

     /*
      * Copy the line number from funcDef_t into tag_t in case they differ.
      * If they did differ, otherwise, the following PrependTagHistory()
      * would record the wrong line number from tag_t.  A lesser possible problem
      * is that doing so could affect an older historical tag (oh well, no real harm).
      */
      pTag->lineInt = pFuncDef->lineStart;
   }
   else
   {
      line = pTag->lineInt ;    //- 1;
   }

  /*
   * Load the module with the tag into text widget.
   * Move the cursor to the line that was tagged.
   */
   if ( LoadTextWidgetWithModule( pTag->pModuleName,
                                  line,
                                  ROW_NONE,
                                  DONT_RELOAD ) == FUNC_OK )
   {
     /*
      * Highlight the tag in the text widget.
      */
      HighlightTag( pTag, -1 );

     /*
      * Record tag (whose tree item was clicked) into tag history.
      */
      PrependTagHistory( pTag );
   }

  /*
   * Restore the active text widget.
   */
   SetActiveTextWidget( pWidgetTextActive = pWidgetTextActivePrev );

   ScheduleUpdateFuncTree();

quit:
   UnlockTreeFunc( &busy_HyperjumpViaTreeNodeOrItem_cb );
   return FALSE;  /* tell GTK+ to stop calling */
}

/*****************************************************************************
 * Idle callback to load text widget with a module corresponding
 * to the subtree (node) selected in the notebook's functree.
 *****************************************************************************/
gint
HyperjumpToModuleViaModuleTree_cb( gpointer pModuleName_ )
{
   GtkWidget*		pWidgetTextActivePrev   = pWidgetTextActive;
   char*			pModuleName				= pModuleName_;

g_return_val_if_fail( pModuleName, FALSE );

  /*
   * --- was copy/pasted from list.c ---
   */

  /*
   * Prevent nesting critical functions.
   */
   if ( !LockTreeFunc( &busy_HyperjumpToModuleViaModuleTree_cb ) )
      return TRUE; /* tell GTK+ to call back again */

  /*
   * Turn off this idle callback now.  Otherwise, it will be called again
   * when the following functions call ProcessPendingEvents()!
   */
   UnscheduleIdleCallback( &id_HyperjumpToModuleViaModuleTree_cb );

  /*
   * Should the module be shown in the main or aux text widget?
   */
   SwitchActiveTextWidget( DONT_SWITCH_UNLESS_AUX_TEXT_KEY );

  /*
   * Load the selected module (ie source file) into the text widget.
   * Move the cursor to the first line.
   */
   if ( LoadTextWidgetWithModule( pModuleName,
                                  0,
                                  ROW_NONE,
                                  DONT_RELOAD )
        != FUNC_OK )
   {
     /*
      * Oops, unable to load the module.
      * (A warning has already been displayed.)
      */
      goto quit;
   }

  /*
   * Clear statusbar.
   */
   MostlyClearStatusbar();

   ScheduleUpdateFuncTree();

quit:
  /*
   * Restore the active text widget.
   */
   SetActiveTextWidget( pWidgetTextActive = pWidgetTextActivePrev );

   UnlockTreeFunc( &busy_HyperjumpToModuleViaModuleTree_cb );
   return FALSE;  /* tell GTK+ to stop calling */
}

/*****************************************************************************
 * @fn void HandlerFuncDefTreeExpand( GtkTreeItem* pWidgetTreeItem, tag_t* pTagFuncCall );
 * When a tree node depicting a function definition is expanded,
 * decorate it with depictions of the function calls it makes.
 *****************************************************************************/
static GtkTreeItem* HandlerFuncDefTreeExpand_pWidgetTreeItem	= NULL;
static tag_t*       HandlerFuncDefTreeExpand_pTagFuncCall		= NULL;
void
HandlerFuncDefTreeExpand( GtkTreeItem* pWidgetTreeItem,
                          tag_t*       pTagFuncCall )
{
  /*
   * Don't start another callback until the first one completes.
   */
   if ( !Lock( &HandlerFuncDefTreeExpand_cb_lock ) )
      return;

   HandlerFuncDefTreeExpand_pWidgetTreeItem	= pWidgetTreeItem;
   HandlerFuncDefTreeExpand_pTagFuncCall	= pTagFuncCall;

   ScheduleIdleCallback( &id_HandlerFuncDefTreeExpand_cb,
                         HandlerFuncDefTreeExpand_cb,
                         NULL );
   return;
}
gint
HandlerFuncDefTreeExpand_cb( gpointer unused )
{
   funcDef_t*	pFuncDef		= NULL;
   GtkTree*		pTree			= NULL;
   GtkTreeItem*	pWidgetTreeItem	= HandlerFuncDefTreeExpand_pWidgetTreeItem;
   tag_t*		pTagFuncCall	= HandlerFuncDefTreeExpand_pTagFuncCall;

  /*
   * Prevent nesting critical functions.
   */
   if ( !LockTreeFunc( &busy_HandlerFuncDefTreeExpand_cb ) )
      return TRUE; /* tell GTK+ to call back again */

   UnscheduleIdleCallback( &id_HandlerFuncDefTreeExpand_cb );

   if ( !pWidgetTreeItem  ||  !pTagFuncCall ) goto quit;

  /*
   * Derive a funcDef_t from the tag.
   * The funcDef_t points to a list of function calls (funcCall_t).
   */
   pFuncDef = FindFuncDefByItsTag( pTagFuncCall );
if ( !pFuncDef ) goto quit;

  /*
   * A GtkTree is needed for the following calls.
   * Convert the tree item widget into a GtkTree.
   */
   pTree = GTK_TREE( GTK_TREE_ITEM_SUBTREE(pWidgetTreeItem) );
if ( !pTree ) goto quit;

  /*
   * Decorate the tree (unless it isn't empty).
   */
   if ( TreeChildrenCount( pTree ) == 0 )
      FillTreeWithFuncCalls( pTree, pFuncDef );

quit:
   Unlock( &HandlerFuncDefTreeExpand_cb_lock );
   UnlockTreeFunc( &busy_HandlerFuncDefTreeExpand_cb );
   return FALSE; /* tell GTK+ to stop calling this idle callback */
}

/*****************************************************************************
 * @fn void HandlerModuleTreeExpand( GtkTreeItem* pWidgetTreeItem, module_t* pModule );
 * When a tree node depicting a module is expanded,
 * decorate it with depictions of the module's function definitions.
 *****************************************************************************/
static GtkTreeItem* HandlerModuleTreeExpand_pWidgetTreeItem		= NULL;
static module_t*    HandlerModuleTreeExpand_pModule				= NULL;
void
HandlerModuleTreeExpand( GtkTreeItem* pWidgetTreeItem,
                         module_t*    pModule )
{
  /*
   * Don't start another callback until the first one completes.
   */
   if ( !Lock( &HandlerModuleTreeExpand_cb_lock ) )
      return;

   HandlerModuleTreeExpand_pWidgetTreeItem	= pWidgetTreeItem;
   HandlerModuleTreeExpand_pModule			= pModule;

   ScheduleIdleCallback( &id_HandlerModuleTreeExpand_cb,
                         HandlerModuleTreeExpand_cb,
                         NULL );
   return;
}
gint
HandlerModuleTreeExpand_cb( gpointer unused )
{
   GtkTree*		pTree			= NULL;
   GtkTreeItem*	pWidgetTreeItem	= HandlerModuleTreeExpand_pWidgetTreeItem;
   module_t*	pModule			= HandlerModuleTreeExpand_pModule;

  /*
   * Prevent nesting critical functions.
   */
   if ( !LockTreeFunc( &busy_HandlerModuleTreeExpand_cb ) )
      return TRUE; /* tell GTK+ to call back again */

   UnscheduleIdleCallback( &id_HandlerModuleTreeExpand_cb );

if ( !pWidgetTreeItem  ||  !pModule ) goto quit;

  /*
   * A GtkTree is needed for the following call.
   * Convert the tree item widget into a GtkTree.
   */
   pTree = GTK_TREE( GTK_TREE_ITEM_SUBTREE(pWidgetTreeItem) );
if ( !pTree ) goto quit;

  /*
   * Decorate the tree (unless it isn't empty).
   */
   if ( TreeChildrenCount( pTree ) == 0 )
      ModuleTreeDecorateSubtree( pTree, pModule );

quit:
   Unlock( &HandlerModuleTreeExpand_cb_lock );
   UnlockTreeFunc( &busy_HandlerModuleTreeExpand_cb );
   return FALSE; /* tell GTK+ to stop calling this idle callback */
}
